package de.hpi.generator.generator.distributors;

import de.hpi.generator.config.DistributionModel;
import de.hpi.generator.config.DistributionModelZIPF;
import de.hpi.generator.generator.storages.ValueSeriaInfo;

/**
 * @author Michael
 *
 */
public class ZipfDistributor extends DataDistributor {

    /**
     * 
     */
    protected double aAlpha;
    /**
     * 
     */
    protected double aZetaCoefficient;
    protected int aMaxDeviation;
    protected long PRECISIONCOEFFICIENT = 100000;
    protected double aRelativityCoefficient = 1.0;

    public String getName() {
        return "ZIPF";
    }

    public ZipfDistributor() throws Exception {
        super();
        initClass(1);
    }

    /**
     * Creates ZipfDistributor for calculations of count of repeat of given value in the given range
     * @param valuesRange a range from 1 to valuesRange
     * @param alpha
     */
    public ZipfDistributor(double alpha) throws Exception {
        super();
        initClass(alpha);
    }

    /**Riemann Zeta function for zipf law
     * @see http://mathworld.wolfram.com/RiemannZetaFunction.html
     * @see http://de.wikipedia.org/wiki/Riemannsche_%CE%B6-Funktion
     * @return 
     */
    protected double zeta() {
        double result = 0;
        double aNextAlpha = this.aAlpha + 1;
        for (long x = 1; x <= PRECISIONCOEFFICIENT; x++) {
            result = result + 1 / Math.pow((double) x, aNextAlpha);
        }
        return result;
    }

    /**
     * Count of repeat of given value in the given range
     * @param valuesNumber a range from 1 to valuesRange
     * @param alpha 
     * @param aValue value for count calculations
     * @return count of repeat of given value in the given range
     */
    public double zipf(int aValue) {
        //percent of given value in the range
        return 1 / (this.aZetaCoefficient * Math.pow(aValue, this.aAlpha));
    }

    /**
     * setup object with specialized params
     * 
     * @param aSpecializedDistributionModel
     *            specialized params
     */
    @Override
    protected void setupObjectWithSpecializedParams(DistributionModel aSpecializedDistributionModel) throws Exception {
        super.setupObjectWithSpecializedParams(aSpecializedDistributionModel);
        //convert common type of aSpecializedDistributionModel object to specialized type
        DistributionModelZIPF aDistributionModelZIPF = (DistributionModelZIPF) aSpecializedDistributionModel;
        //setup distributor parameters
        this.initClass(aDistributionModelZIPF.alpha);
    }

    protected void initClass(double alpha) throws Exception {
        this.aAlpha = alpha;
        this.aZetaCoefficient = zeta();
    }

    @Override
    public void setupRange(int aValuesCount) throws Exception {
        super.setupRange(aValuesCount);
        recalculateParams();
        this.random.calibrate(1, this.aMaxDeviation);


    }

    @Override
    public int getCountOfValue(int aValue) throws Exception {
        return (int) Math.round(getCleanCountOfValue(aValue) * this.aRelativityCoefficient);
    }

    public int getCleanCountOfValue(int aValue) throws Exception {
        return (int) Math.round(this.aNumberOfValuesToGenerate * (zipf(aValue)));// * this.aRelativityCoefficient
    }

    @Override
    public ValueSeriaInfo generateNextRandomValueSeriaInfo() throws Exception {
        int aRandomInteger = random.getRandomValue();
        int aRandomIntegerCount = this.getCountOfValue(aRandomInteger);

        return new ValueSeriaInfo(aRandomInteger, aRandomIntegerCount);
    }

    protected void recalculateParams() throws Exception {
        calculateRelativityCoefficient();
        calculateAllValuesCountAndMaxDeviation();
        
    }

    private void calculateAllValuesCountAndMaxDeviation() throws Exception {
        this.aAllValuesCount = 0;

        for (this.aMaxDeviation = 1; this.aMaxDeviation < this.aNumberOfValuesToGenerate; this.aMaxDeviation++) {
            int aCurrentCleanCountOfValue = getCountOfValue(aMaxDeviation);
            if (aCurrentCleanCountOfValue < 1) {
                break;
            }
           
            this.aAllValuesCount += aCurrentCleanCountOfValue;
        }
        this.aMaxDeviation--;
    }

    private void calculateRelativityCoefficient() throws Exception {
        this.aAllValuesCount = 0;

        for (this.aMaxDeviation = 1; this.aMaxDeviation < this.aNumberOfValuesToGenerate; this.aMaxDeviation++) {
            int aCurrentCleanCountOfValue = getCleanCountOfValue(aMaxDeviation);
            if (aCurrentCleanCountOfValue < 1) {
                break;
            }
            this.aAllValuesCount += aCurrentCleanCountOfValue;
        }

        aRelativityCoefficient = (double) this.aNumberOfValuesToGenerate / (double) this.aAllValuesCount;
    }
}
